/**
 * Copyright 2009 Jesse Brown (jbrown@fluentv.com)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *   http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package com.fluentv.games.netblocks;

import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.View;

import com.fluentv.games.netblocks.piece.Shape;

/**
 * Abstract class that expects a cell oriented field, where each cell
 * has a Shape (with corresponding color).
 * 
 * @author Jesse Brown (jbrown@fluentv.com)
 */
public abstract class DrawableTetrisField extends View {
  private Map<Integer, Paint> cacheCellColorDrawables = new HashMap<Integer, Paint>(16);
  
  public DrawableTetrisField(Context context) {
    super(context);
  }

  /**
   * Implementations must return the cell height attribute of the field.
   */
  abstract protected int getCellsHigh();

  /**
   * Implementations must return the cell width attribute of the field.
   */
  abstract protected int getCellsWide();
   
  /**
   * Get the Shape at the X (widthCells) and Y (heightCells) co-ordinates.
   * 
   * @param cellX
   *          1 to widthCells cell address
   * @param cellY
   *          1 to heightCells cell address
   * @return Shape at the specified cell address. If no shape found, will be
   *         null
   * @throws IllegalArgumentException
   *           If the cell coordinates are out of range
   */
  abstract protected Shape getCell(int cellX, int cellY);
  
  /**
   * Background Color that will be laid down first
   */
  abstract protected int getBackgroundColor();
  
  /**
   * Paint used to draw the crosshatches between cells.
   * @return null to disable, or the Paint to use.
   */
  abstract protected Paint getInterCellCrossHatchPaint();
  
  /**
   * Paint used to draw the border
   * @return null to disable or the Paint to use.
   */
  abstract protected Paint getBorderPaint();
  
  /**
   * Method called to draw a specific cell. Passed in the exact cell co-ordinates drawn as well as the Shape that should be drawn
   * in the cell.
   */
  protected void drawCell(Canvas canvas, Shape cellShape, int cellCanvasLeftX, int cellCanvasTopY, int cellCanvasRightX, int cellCanvasBottomY) {
    
    // Draw our rectangle using cached Paint objects (to avoid excess GC work)
    Paint paint = cacheCellColorDrawables.get(cellShape.getColor());
    if (paint == null) {
      paint = new Paint();
      paint.setColor(cellShape.getColor());
      paint.setAntiAlias(true);
      cacheCellColorDrawables.put(cellShape.getColor(), paint);
    }
    
    canvas.drawRect(
        cellCanvasLeftX, cellCanvasTopY, cellCanvasRightX, cellCanvasBottomY, paint);                  
  }
  
  /**
   * Paint our field
   */
  @Override
  public synchronized void onDraw(Canvas canvas) {
    // Clear the background
    canvas.drawColor(getBackgroundColor());

    // Constants while drawing.
    final int canvasWidth = getWidth() - 1;
    final int canvasHeight = getHeight() - 1;
    final int cellsWide = getCellsWide();
    final int cellsHigh = getCellsHigh();    
    final int cellWidth = canvasWidth / cellsWide;
    final int cellHeight = canvasHeight / cellsHigh;
    
    // Offset our starting positions by centering ourselves using the pixels remaining
    // after dividing up into the number of cells we have.
    // Make sure our start and end positions have an even multiple of the cell width
    // between them. 
    final int widthOffset = (canvasWidth % cellsWide) / 2;
    final int heightOffset = (canvasHeight % cellsHigh) / 2;    
    final int widthLimit = widthOffset + (cellsWide * cellWidth);
    final int heightLimit = heightOffset + (cellsWide * cellHeight);
        
    // Cross-hatch our field on block boundaries.
    Paint crossHatchPaint = getInterCellCrossHatchPaint();
    
    // Draw our X and Y lines.
    if (crossHatchPaint != null) {
      for (int y = 0; y < cellsHigh; y++) {
        int startX = widthOffset;
        int endX = widthLimit;
        int yAxis = (y * cellHeight) + heightOffset;      

        canvas.drawLine(startX, yAxis, endX, yAxis, crossHatchPaint);
      }

      for (int x = 0; x < cellsWide; x++) {
        int startY = heightOffset;
        int endY = heightLimit;
        int xAxis = (x * cellWidth) + widthOffset;      

        canvas.drawLine(xAxis, startY, xAxis, endY, crossHatchPaint);
      }
    }
            
    // Make a small border around the field.
    Paint borderPaint = getBorderPaint();
    if (borderPaint != null) {
      canvas.drawRect(widthOffset, heightOffset, widthLimit, heightLimit, borderPaint);
    }
    
    // Process each cell in our field matrix, drawing them one at a time.
    for (int y = 0; y < cellsHigh; y++) {
      for (int x = 0; x < cellsWide; x++) {
        
        Shape cellValue = getCell(x + 1, y + 1);
        if (cellValue != null) {
          
          // Shrink our drawing area by 1 pixel in all directions so we don't overwrite
          // our cross hatching and each block has a defined area. 
          int cellCanvasLeftX = ((x * cellWidth) + widthOffset) + 1;
          int cellCanvasTopY = ((y * cellHeight) + heightOffset) + 1;
          int cellCanvasRightX = cellCanvasLeftX + cellWidth - 1;
          int cellCanvasBottomY = cellCanvasTopY + cellHeight - 1;
          
          drawCell(canvas, cellValue, cellCanvasLeftX, cellCanvasTopY, cellCanvasRightX, cellCanvasBottomY);
        }
      }
    }
  }
  
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    int cellsWide = getCellsWide();
    int cellsHigh = getCellsHigh();
    
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    // If we are unspecified, make up desired dimensions.
    int widthMeasure = widthSize;
    if (widthMode == MeasureSpec.UNSPECIFIED) {
      widthMeasure = cellsWide * 20;
    }  
        
    int heightMeasure = heightSize;
    if (heightMode == MeasureSpec.UNSPECIFIED) {
      heightMeasure = cellsHigh * 20;
    } 
    
    int widthCellSize = widthMeasure / cellsWide;
    int heightCellsSize = heightMeasure / cellsHigh;
    
    int smallestCellSize = widthCellSize;
    if (smallestCellSize > heightCellsSize) {
      smallestCellSize = heightCellsSize;
    }
   
    heightMeasure = smallestCellSize * cellsHigh;
    widthMeasure = smallestCellSize * cellsWide;
 
    super.setMeasuredDimension(widthMeasure, heightMeasure);
  }   
}
